package fr.lelouet.monitoring.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.lelouet.monitoring.DataHandler;
import fr.lelouet.monitoring.UsageMonitor;
import fr.lelouet.monitoring.datahandlers.LoggerDataHandler;
import fr.lelouet.tools.SequentialIterator;
import fr.lelouet.tools.sequentialIterators.Pulsed;

/** monitors the local virtual machines. Only works for linux, with perf, and kvm virtual machines<br />
 * when running, loop:
 * <ol>
 * <li>guess what vms are running</li>
 * <li>create or destroy {@link ProcessMonitor} according to the vms changes</li>
 * <li>request data from the monitors</li>
 * </ol>
 * */
class KvmHypervisor implements Runnable{
	
	public static final String HANDLERS_KEY="data.handlers"
	public static final String REQUESTSCHEDULER_KEY="scheduler.strategy"
	public static final String POWERURI_KEY="power.uri" 
	
	private static final Logger logger = LoggerFactory.getLogger(KvmHypervisor.class)
	
	/** default handler for data*/
	public static final java.lang.Class defaultHandlerClass= LoggerDataHandler.class
	
	/** the default scheduler*/
	public static final java.lang.Class DEFAULT_SCHEDULER_CLASS = Pulsed.class
	
	protected final Properties props
	
	/** @return the handler specified in the Properties, or the default LoggerHandler factory
	 * @param prop the properties used to configure this*/
	public static List<DataHandler> findHandlers(Properties prop) {
		String handlersClasses = prop.getProperty(HANDLERS_KEY,defaultHandlerClass.getCanonicalName())
		List ret = []
		handlersClasses.split(" ").each {
			try {
				DataHandler handler = Class.forName(it).newInstance()
				handler.configure(prop)
				ret.add(handler)
			} catch(ClassNotFoundException e) {
				logger.error("cannot load Handler class "+it)
			}
		}
		return ret
	}
	
	/** @return the {@link RequestScheduler} specified in the properties, or a default one if none is present*/
	public static SequentialIterator<ProcessMonitor> findRequestScheduler(Properties prop) {
		String schedulerClass = prop.getProperty(REQUESTSCHEDULER_KEY, DEFAULT_SCHEDULER_CLASS.getCanonicalName())
		try {
			SequentialIterator<ProcessMonitor> ret=Class.forName(schedulerClass).newInstance()
			ret.configure(prop)
			logger.info("succesfuly generated the scheduler from class : "+schedulerClass)
			return ret
		} catch(ClassNotFoundException e) {
			logger.error("cannot load class "+schedulerClass+", using default scheduler")
			SequentialIterator<ProcessMonitor> ret= DEFAULT_SCHEDULER_CLASS.newInstance()
			ret.configure(prop)
			logger.error("created default scheduler : "+ret.getClass().getCanonicalName())
			return ret
		}
	}
	
	/** the the handlers to add to each each {@link ProcessMonitor} created*/
	protected List<DataHandler> handlers=new ArrayList<DataHandler>();
	
	/**  @see KvmHypervisor#handlerFactory*/
	public void setHandlers(List<DataHandler> handlers) {
		this.handlers = handlers
		monitoredResources.values().each{
			((UsageMonitor)it).setHandlers(handlers)
		}
		powerMonitor?.setHandlers handlers
	}
	
	/** the {@link RequestScheduler} used to wait between the requests*/
	protected SequentialIterator<ProcessMonitor> sequentialIterator;
	
	public void setRequestScheduler(SequentialIterator requestScheduler) {
		this.sequentialIterator = requestScheduler;
	}
	
	/** @param prop the properties used by the monitor*/
	public KvmHypervisor(Properties prop) {
		this.props = prop
		setHandlers (findHandlers(props) )
		setRequestScheduler(findRequestScheduler(props))
		findPowerMonitor()
	}
	
	/** create a {@link KvmHypervisor} with default values*/
	public KvmHypervisor() {
		this(new Properties())
	}
	
	/** has the loop to be stopped ?*/
	protected boolean toStop=false
	
	/** ask this to stop as soon as it can*/
	public void stop() { toStop=true
	}
	
	/** is this required to stop ?*/
	public boolean hasToStop() {
		return toStop
	}
	
	/** make one loop of monitoring*/
	protected void monitoringLoop() {
		logger.trace("starting the loop")
		checkVMs()
		launchMonitors()
	}
	
	public void run() {
		toStop = false
		while(! hasToStop()) {
			monitoringLoop()
		}
	}
	
	/** ressource->ProcessMonitor(pid)*/
	Map<String, UsageMonitor> monitoredResources= [:]
	
	UsageMonitor powerMonitor
	
	/** check in {@link #props} and generate a plugwise for each macadress*/
	protected void findPowerMonitor() {
		String powerURI = props.getProperty(POWERURI_KEY,"")
		if(powerURI!=null && powerURI.length()>0) {
			logger.info("power sensor for <${powerURI}>")
			if(powerURI.toLowerCase().startsWith("plugwise://")) {
				String plugwiseMac = powerURI.substring (1+powerURI.lastIndexOf ('/') )
				PlugwisedHypervisor pwhv = new PlugwisedHypervisor(plugwiseMac)
				pwhv.configure(props)
				pwhv.setHandlers(handlers)
				powerMonitor=pwhv
			} else {
				logger.error("protocole not supported for power monitor $powerURI")
			}
		}
	}
	
	/** update the vms monitors on the local hypervisor*/
	protected void checkVMs() {
		Set toRemove = new HashSet<String>()
		toRemove.addAll(monitoredResources.keySet())
		detectVMs().findAll{it.length()>0 }.each() {
			String[]data = it.split(" ")
			String pid = "pid:"+data[0]
			if( monitoredResources.containsKey(pid)){
				toRemove.remove(pid)
			} else {
				logger.info("new VM to monitor : {}", it)
				try {
					UsageMonitor monitor = generateMonitorForPID(Integer.parseInt(data[0]))
					monitoredResources[pid]= monitor
				} catch(NumberFormatException nfe) {
					
					logger.error nfe.getMessage()
				}
			}
		}
		toRemove.each {
			String key=it
			if(key.startsWith("pid:")) {
				logger.info "removing unexistent vm : {}", key
				monitoredResources.remove(key)
			}
		}
	}
	
	protected static final String LINE_SEPARATOR = System.getProperty("line.separator");
	
	/** @return an array of existing Strings for vms on local hypervisor<br />
	 * local means the one that run the java machine. The strings are formated as &lt;pid&gt;"whitespace"&lt;command&gt;*/
	protected String[] detectVMs() {
		List ret = []
		try {
			Process proc = [
				"bash",
				"-c",
				"ps -e -o pid,comm | grep \"\\(kvm\\|java\\)\$\"  | sed \"s/^ \\+//\""
			].execute()
			proc.waitFor()
			String out = proc.in.text
			if(out.length()!=0) { 
				out.split(LINE_SEPARATOR).each{ ret.add(it) }
			}
		} catch(Exception e) {
			logger.warn "{} @ {}",e.toString(), e.getStackTrace()[0]
		}
		return ret
	}
	
	/** create a {@link ProcessMonitor} monitoring given pid*/
	protected UsageMonitor generateMonitorForPID(int pid) {
		ProcessMonitor ret = new ProcessMonitor(pid, "localhost")
		ret.configure(props)
		ret.setHandlers(handlers)
		return ret
	}
	
	/** ask each {@link ProcessMonitor} to get its informations, wait for all of them to have received data, then retrieve the power consumption*/
	protected void launchMonitors() {
		SequentialIterator scheduler = sequentialIterator
		scheduler.startNewLoop(monitoredResources.values())
		if(hasToStop()) { return
		}
		scheduler.each { it.gatherInformations() }
		monitoredResources.values().each{
			UsageMonitor monitor = it 
			logger.debug ("waiting for monitor {}", monitor)
			while(!monitor.informationGathered() && !hasToStop()) {
				Thread.yield()
			}
		}
		if(hasToStop()) { return
		}
		logger.debug("starting the power monitor")
		if(powerMonitor!=null) {
			powerMonitor.gatherInformations()
			while(!powerMonitor.informationGathered() && ! hasToStop()) {
				Thread.yield()
			}
		}
		logger.debug "monitors launched and result grabbed"
	}
}

